//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#ifndef __ELASTOS_I8042_H__
#define __ELASTOS_I8042_H__

//
// I/O port accesses, and definitions for the i8042
//

#define i8042_READ_STATUS_PORT(_p_)     Inb((ioport_t)((ioport_t)(_p_) + 4))
#define i8042_WRITE_CMD_PORT(_p_, _c_)  \
                                Outb((ioport_t)((ioport_t)(_p_) + 4), (_c_))
#define i8042_READ_DATA_PORT(_p_)       Inb((ioport_t)(_p_))
#define i8042_WRITE_DATA_PORT(_p_, _c_) Outb((ioport_t)(_p_),(_c_))

// Timeout in ms for keyboard command acknowledge, 1000ms
#define KBD_TIMEOUT         50000

//
// Status Register Bits
//

#define KBD_STAT_OBF        0x01    // Keyboard output buffer full
#define KBD_STAT_IBF        0x02    // Keyboard input buffer full
#define KBD_STAT_SELFTEST   0x04    // Self test successful
#define KBD_STAT_CMD        0x08    // Last write was a command write (0=data)
#define KBD_STAT_UNLOCKED   0x10    // Zero if keyboard locked
#define KBD_STAT_MOUSE_OBF  0x20    // Mouse output buffer full
#define KBD_STAT_GTO        0x40    // General receive/xmit timeout
#define KBD_STAT_PERR       0x80    // Parity error

#define AUX_STAT_OBF (KBD_STAT_OBF | KBD_STAT_MOUSE_OBF)

//
// Keyboard Controller Commands
//
#define I8042_CMD_READ_MODE         0x20    // Read mode bits
#define I8042_CMD_WRITE_MODE        0x60    // Write mode bits
#define I8042_CMD_GET_VERSION       0xA1    // Get controller version
#define I8042_CMD_MOUSE_DISABLE     0xA7    // Disable mouse interface
#define I8042_CMD_MOUSE_ENABLE      0xA8    // Enable mouse interface
#define I8042_CMD_TEST_MOUSE        0xA9    // Mouse interface test
#define I8042_CMD_SELF_TEST         0xAA    // Controller self test
#define I8042_CMD_KBD_TEST          0xAB    // Keyboard interface test
#define I8042_CMD_KBD_DISABLE       0xAD    // Keyboard interface disable
#define I8042_CMD_KBD_ENABLE        0xAE    // Keyboard interface enable
#define I8042_CMD_KBD_READOUPUT     0xD0    // Read output port
#define I8042_CMD_WRITE_AUX_OBUF    0xD3    // Write to output buffer as if
                                            // initiated by the auxiliary
                                            // device
#define I8042_CMD_WRITE_MOUSE       0xD4    // Write the following byte
                                            // to the mouse

//
// Keyboard Commands
//
#define KBD_CMD_SET_LEDS        0xED    // Set keyboard leds
#define KBD_CMD_CODE_SET        0xF0    // Set scan code
#define KBD_CMD_SET_RATE        0xF3    // Set typematic rate
#define KBD_CMD_ENABLE          0xF4    // Enable scanning
#define KBD_CMD_DISABLE         0xF5    // Disable scanning
#define KBD_CMD_RESET           0xFF    // Reset
#define KBD_CMD_ECHO            0xEE    // Echo

//
// Mouse commands
//
#define MS_CMD_RESET            0xFF    // Reset
#define MS_CMD_DISABLE          0xF5    // Disable data reporting
#define MS_CMD_ENABLE           0xF4    // Enable data reporting
#define MS_CMD_SETSAMPLERATE    0xF3    // Set sample rate
#define MS_CMD_GETID            0xF2    // Get device ID
#define MS_CMD_SETSTREAMMODE    0xEA    // Set stream mode

//
//Controller Mode Register Bits
//
#define I8042_CTRL_KBD_INT      0x01    // Keyboard data generage IRQ1
#define I8042_CTRL_MS_INT       0x02    // Mouse data generate IRQ12
#define I8042_CTRL_SYS          0x04    // The system flag
#define I8042_CTRL_NO_KEYLOCK   0x08    // The keylock doesn't affect the
                                        // keyboard if set
#define I8042_CTRL_DISABLE_KBD  0x10    // Disable keyboard interface
#define I8042_CTRL_DISABLE_MS   0x20    // Disable mouse interface
#define I8042_CTRL_KCC          0x40    // Scan code conversion to PC format
#define I8042_CTRL_RFU          0x80    // The system reserve

//
// Keyboard Replies
//
#define CMD_REPLY_POR           0xAA    // Power on reset
#define CMD_REPLY_ACK           0xFA    // Command ACK
#define CMD_REPLY_RESEND        0xFE    // Command NACK, send the cmd again

//
// The led control key hold
//
#define KBD_CAPSLOCK_HOLD       0x0400
#define KBD_NUMLOCK_HOLD        0x0200
#define KBD_SCROLLLOCK_HOLD     0x0100

//
// CTRL, ALT, SHIFT status
//
#define KBD_CTRL                0x0001
#define KBD_ALT                 0x0002
#define KBD_SHIFT               0x0004
#define KBD_LCTRL               0x0100
#define KBD_LALT                0x0200
#define KBD_LSHIFT              0x0400
#define KBD_RCTRL               0x1000
#define KBD_RALT                0x2000
#define KBD_RSHIFT              0x4000

//
// The follow's operatoin be improve in the multiprocesser entironment.
// [juncheng_lau]
//

INLINE int KBDHasData(ioport_t Port)
{
    return (i8042_READ_STATUS_PORT(Port) & KBD_STAT_OBF);
}

#define KBD_RECEIVE_INTERRUPT(_p_) \
    ((i8042_READ_STATUS_PORT(_p_) & (KBD_STAT_MOUSE_OBF | KBD_STAT_OBF)) \
        == KBD_STAT_OBF)

#define MOUSE_RECEIVE_INTERRUPT(_p_) \
    ((i8042_READ_STATUS_PORT(_p_) & (KBD_STAT_MOUSE_OBF | KBD_STAT_OBF)) \
    == (KBD_STAT_MOUSE_OBF | KBD_STAT_OBF))

INLINE int I8042SendCommand(ioport_t Port, uint8_t uData)
{
    int i = 0;
    while(i8042_READ_STATUS_PORT(Port) & KBD_STAT_IBF) {
        if (i++ == KBD_TIMEOUT) {
#ifdef _DEBUG
            kprintf("I8042SendCommand 0x%02x failed, The status = 0x%02x\n", \
                                        uData, i8042_READ_STATUS_PORT(Port));
#endif
            return 1;
        }
        DzDelay(10);
    }

    i8042_WRITE_CMD_PORT(Port, uData);

    return 0;
}

INLINE int I8042SendData(ioport_t Port, uint8_t uData)
{
    int i = 0;

    while(i8042_READ_STATUS_PORT(Port) & KBD_STAT_IBF) {
        if (i++ == KBD_TIMEOUT) {
#ifdef _DEBUG
            kprintf("I8042SendData 0x%02x failed, The status = 0x%02x\n", \
                                        uData, i8042_READ_STATUS_PORT(Port));
#endif
            return 1;
        }
        DzDelay(10);
    }

    i8042_WRITE_DATA_PORT(Port, uData);

    return 0;
}

INLINE int I8042RecvData(ioport_t Port, uint8_t *puData)
{
    int i = 0;

    while ((i8042_READ_STATUS_PORT(Port) & KBD_STAT_OBF) == 0) {
        if (i++ == KBD_TIMEOUT) {
#ifdef _DEBUG
            kprintf("I8042RecvData failed, The status = 0x%02x\n", \
                                        i8042_READ_STATUS_PORT(Port));
#endif
            return 1;
        }
        DzDelay(10);
    }

    *puData = i8042_READ_DATA_PORT(Port);

    return 0;
}

INLINE void MouseEnableInterrupt(ioport_t Port)
{
    uint8_t uChar;

    I8042SendCommand(Port, I8042_CMD_READ_MODE);
    I8042RecvData(Port, &uChar);

    // clear disable mouse bit
    //
    uChar &= ~I8042_CTRL_DISABLE_MS;

    // set interrupt enable bit
    //
    uChar |=  I8042_CTRL_MS_INT;

    I8042SendCommand(Port, I8042_CMD_WRITE_MODE);
    I8042SendData(Port, uChar);
}

INLINE void MouseDisableInterrupt(ioport_t Port)
{
    uint8_t uChar;

    I8042SendCommand(Port, I8042_CMD_READ_MODE);
    I8042RecvData(Port, &uChar);

    // clear mouse interrupt enable bit
    uChar &=  ~I8042_CTRL_MS_INT;
    uChar |= I8042_CTRL_DISABLE_MS;

    I8042SendCommand(Port, I8042_CMD_WRITE_MODE);
    I8042SendData(Port, uChar);
}

INLINE void KbdEnableInterrupt(ioport_t Port)
{
    uint8_t uChar;

    I8042SendCommand(Port, I8042_CMD_READ_MODE);
    I8042RecvData(Port, &uChar);

    // clear disable keyboard bit
    //
    uChar &= ~I8042_CTRL_DISABLE_KBD;

    // set interrupt enable bit, and set-1 xlate
    //
    uChar |=  (I8042_CTRL_KCC | I8042_CTRL_KBD_INT);

    I8042SendCommand(Port, I8042_CMD_WRITE_MODE);
    I8042SendData(Port, uChar);
}

INLINE void KbdDisableInterrupt(ioport_t Port)
{
    uint8_t uChar;

    I8042SendCommand(Port, I8042_CMD_READ_MODE);
    I8042RecvData(Port, &uChar);

    // set disable keyboard bit
    //
    uChar |= I8042_CTRL_DISABLE_KBD;

    // set interrupt enable bit, and set-1 xlate
    //
    uChar &=  ~(I8042_CTRL_KCC | I8042_CTRL_KBD_INT);

    I8042SendCommand(Port, I8042_CMD_WRITE_MODE);
    I8042SendData(Port, uChar);
}

//
// Send keyboard command and argument can use this func
//
INLINE int SendData2KBD(ioport_t Port, uint8_t uData)
{
    uint8_t uRetVal = 0;

    do {
        if (I8042SendData(Port, uData)) {
            return 1;
        }
        if (I8042RecvData(Port, &uRetVal)) {
            return 1;
        }

        if (CMD_REPLY_ACK == uRetVal) {
            break;
        }
        if (CMD_REPLY_RESEND == uRetVal) {
            return 1;
        }
    } while (1);

    return 0;
}

INLINE int SendData2Mouse(ioport_t Port, uint8_t uData)
{
    uint8_t uRetVal = 0;

    do {
        if (I8042SendCommand(Port, I8042_CMD_WRITE_MOUSE)) {
            return 1;
        }
        if (I8042SendData(Port, uData)) {
            return 1;
        }
        if (I8042RecvData(Port, &uRetVal)) {
            return 1;
        }

        if (CMD_REPLY_ACK == uRetVal) {
            break;
        }
        if (CMD_REPLY_RESEND == uRetVal) {
            return 1;
        }
    } while (1);

    return 0;
}

INLINE void EnableReboot(ioport_t Port)
{
    uint8_t uChar;

    I8042SendCommand(Port, I8042_CMD_READ_MODE);
    I8042RecvData(Port, &uChar);

    uChar |=  I8042_CTRL_SYS;

    I8042SendCommand(Port, I8042_CMD_WRITE_MODE);
    I8042SendData(Port, uChar);
}

#endif
